#pragma once

#include <boost/date_time.hpp>
#include <services/future.hpp>

#include "exports.hpp"




namespace Engine
{
	namespace Debugger
	{
		class timeout
			: public std::exception
		{};

		class communication_failure
			: public std::exception
		{};

		class command_failed
			: public std::exception
		{};

		/**
		 * This task "glues" together a future, a command and it's response: a notification.
		 *
		 * It exposes one interface for any type of Command / Notification pair, so that the Controller
		 * can simply update the task, once the right notification has arrived, or when the task
		 * timed out.
		 */
		class ResponseTask
		{
		public:

			SHARED(ResponseTask);

			template <typename Command_t, typename return_type>
			ResponseTask(services::future<return_type> future, const Command_t& command, boost::posix_time::time_duration timeout)
				: m_tag(command.tag())
				, m_start(boost::posix_time::microsec_clock::local_time())
				, m_timeout(timeout)
			{
				// Every command tells us which notification type it wishes
				typedef Command_t::response_type   response_type;

				m_setResponse = [future](Response::ptr response) mutable
				{
					auto required_type = boost::shared_dynamic_cast<response_type>(response);
					if(!required_type)
					{
						future.set_exception(std::copy_exception(communication_failure()));
					}
					else
					{
						if(required_type->failed())
						{
							future.set_exception(std::copy_exception(command_failed()));
						}
						else
						{
							future.set_value(required_type->value());
						}
					}
				};

				m_setTimeout = [future]() mutable
				{
					future.set_exception(std::copy_exception(Debugger::timeout()));
				};
			}

			template <typename Command_t>
			ResponseTask(services::future<void> future, const Command_t& command, boost::posix_time::time_duration timeout)
				: m_tag(command.tag())
				, m_start(boost::posix_time::microsec_clock::local_time())
				, m_timeout(timeout)
			{
				// Every command tells us which notification type it wishes
				typedef Command_t::response_type   response_type;

				m_setResponse = [future](Response::ptr response) mutable
				{
					auto required_type = boost::shared_dynamic_cast<response_type>(response);
					if(!required_type)
					{
						future.set_exception(std::copy_exception(communication_failure()));
					}
					else
					{
						future.set_value();
					}
				};

				m_setTimeout = [future]() mutable
				{
					future.set_exception(std::copy_exception(Debugger::timeout()));
				};
			}



			boost::uuids::uuid tag() const
			{
				return m_tag;
			}

			void setResponse(Response::ptr response)
			{
				m_setResponse(response);
			}

			/**
			 * Tests if the task has timed out.
			 */
			bool hasTimedOut()
			{
				if(boost::posix_time::microsec_clock::local_time() > m_start + m_timeout)
				{
					m_setTimeout();
					return true;
				}
				else
				{
					return false;
				}
			}

		private:

			boost::uuids::uuid                     m_tag;
			boost::posix_time::ptime               m_start;
			boost::posix_time::time_duration       m_timeout;

			boost::function<void(Response::ptr)>   m_setResponse;
			boost::function<void()>                m_setTimeout;
		};
		///////////////////////////////////////////////////////////////////////////////////////////
	}
	///////////////////////////////////////////////////////////////////////////////////////////////
}
///////////////////////////////////////////////////////////////////////////////////////////////////
